package comviva_2014.SmartMapper;

import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;


public class PrimitiveIntHashMap {
		static final int DEFAULT_INITIAL_CAPACITY = 16;
		static final int MAXIMUM_CAPACITY = 1 << 30;
		static final float DEFAULT_LOAD_FACTOR = 0.75f;
		Entry[] table;
		LinkedEntry header;

		int size;
		int threshold;
		final float loadFactor;

		public PrimitiveIntHashMap(int initialCapacity) {
			this.loadFactor = 0.75f;
			threshold = (int) (initialCapacity * loadFactor);
			table = new Entry[initialCapacity];

			// init
			header = new LinkedEntry(-1, -1, null, null);
			header.before = header.after = header;
		}

		public boolean put(int key, byte[] value) {
			// System.out.println("Put Key : " + key);
			// System.out.println("Put Value: " + value);
			int hash = hash(key);
			int i = indexFor(hash, table.length);
			for (Entry e = table[i]; e != null; e = e.next) {
				if (e.hash == hash && e.key == key) {
					e.value = value;
					return false;
				}
			}

			addEntry(hash, key, value, i);
			return true;
		}

		static int hash(int hh) {
			int h = (int) (hh ^ (hh >>> 32));
			h ^= (h >>> 20) ^ (h >>> 12);
			return h ^ (h >>> 7) ^ (h >>> 4);
		}

		static int indexFor(int h, int length) {
			return h & (length - 1);
		}

		void addEntry(int hash, int key, byte[] value, int bucketIndex) {

			Entry old = table[bucketIndex];
			LinkedEntry e = new LinkedEntry(hash, key, value, old);
			table[bucketIndex] = e;
			e.addBefore(header);
			size++;

			if (size++ >= threshold)
				resize(2 * table.length);
		}

		void resize(int newCapacity) {
			Entry[] oldTable = table;
			int oldCapacity = oldTable.length;
			if (oldCapacity == MAXIMUM_CAPACITY) {
				threshold = Integer.MAX_VALUE;
				return;
			}

			Entry[] newTable = new Entry[newCapacity];
			transfer(newTable);
			table = newTable;
			threshold = (int) (newCapacity * loadFactor);
		}

		void transfer(Entry[] newTable) {
			Entry[] src = table;
			int newCapacity = newTable.length;
			for (int j = 0; j < src.length; j++) {
				Entry e = src[j];
				if (e != null) {
					src[j] = null;
					do {
						Entry next = e.next;
						int i = indexFor(e.hash, newCapacity);
						e.next = newTable[i];
						newTable[i] = e;
						e = next;
					} while (e != null);
				}
			}
		}

		final byte[] get(int key) {
			int hash = hash(key);

			for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
				if (e.hash == hash && e.key == key)
					return e.value;
			}
			return null;

		}

		final Entry getEntry(int key) {
			int hash = hash(key);
			for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
				if (e.hash == hash && e.key == key)
					return e;
			}
			return null;
		}

		public int size() {
			return size;
		}

		private final class EntrySet extends AbstractSet<Entry> {
			public Iterator<Entry> iterator() {
				return newEntryIterator(); // returns a HashIterator...
			}

			// ...

			@Override
			public int size() {
				return size;
			}
		}

		Iterator<Entry> newEntryIterator() {
			return new EntryIterator();
		}

		private class EntryIterator extends LinkedHashIterator<Entry> {
			public Entry next() {
				return nextEntry();
			}
		}

		private abstract class LinkedHashIterator<T> implements Iterator<T> {
			LinkedEntry nextEntry = header.after;
			LinkedEntry lastReturned = null;

			public boolean hasNext() {
				return nextEntry != header;
			}

			public void remove() {
				if (lastReturned == null)
					throw new IllegalStateException();
				/*
				 * PrimitiveIntHashMap.this.remove(lastReturned.key);
				 * lastReturned = null;
				 */
			}

			LinkedEntry nextEntry() {
				if (nextEntry == header)
					throw new NoSuchElementException();

				LinkedEntry e = lastReturned = nextEntry;
				nextEntry = e.after;
				return e;
			}
		}

		static class Entry {
			final int key;
			byte[] value;
			Entry next;
			final int hash;

			Entry(int h, int k, byte[] v, Entry n) {
				next = n;
				key = k;
				value = v;
				hash = h;
			}

			public final int getKey() {
				return key;
			}

			public final byte[] getValue() {
				return value;
			}

			public final byte[] setValue(byte[] newValue) {
				byte[] oldValue = value;
				value = newValue;
				return oldValue;
			}

			public final boolean equals(Object o) {
				if (!(o instanceof Entry))
					return false;
				Entry e = (Entry) o;
				if (this.key == e.key) {
					if(Arrays.equals(this.value, e.getValue()))
						return true;
				}
				return false;
			}

		}

		static class LinkedEntry extends Entry {
			LinkedEntry before, after;

			LinkedEntry(int hash, int key, byte[] value, Entry next) {
				super(hash, key, value, next);
			}

			private void remove() {
				before.after = after;
				after.before = before;
			}

			/**
			 * Inserts this entry before the specified existing entry in the
			 * list.
			 */
			private void addBefore(LinkedEntry existingEntry) {
				after = existingEntry;
				before = existingEntry.before;
				before.after = this;
				after.before = this;
			}

		}

		public EntrySet entrySet() {
			return new EntrySet();
		}

		public boolean containsKey(int key) {
			return getEntry(key) != null;
		}

	}
